﻿using Com.Lancetinc.Evs.Helpers;
using Com.Lancetinc.Evs.Models;
using NLog;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Threading;

namespace Com.Lancetinc.Evs.Communications
{
    public class InstrumentManager : IInstrumentManager
    {
        private readonly int KEEP_ALIVE_MILLSECONDS = 10000;

        private static readonly Logger logger = LogManager.GetCurrentClassLogger();

        private readonly GenericInstrumentState instrument;
        private readonly IConnection connection;
        private readonly System.Timers.Timer timer;
        private readonly ConnectionAliveKeeper aliveKeeper;
        private readonly BeatProtocolManager _beatProtocolManager;

        private long _latestReceivedMills = 0;

        public event EventHandler<PacketReceivedEventArgs> PacketReceived;

        #region StateChanged
        private readonly FastSmartWeakEvent<EventHandler<InstrumentInfoChangedEventArgs>> _stateChangedEvent =
            new();

        public event EventHandler<InstrumentInfoChangedEventArgs> StateChanged
        {
            add => _stateChangedEvent.Add(value);
            remove => _stateChangedEvent.Remove(value);
        }

        private void NotifyStateChanged(InstrumentInfoChangedEventArgs args)
        {
            if (!_stateChangedEvent.HasListeners)
                return;
            _stateChangedEvent.Raise(this, args);
        }

        #endregion


        private Dispatcher dispatcher;

        private readonly ManualResetEvent waitResponseSignalEvent = new(false);
        private Packet sendPacketToWaitResponse;
        private Packet responsePacketForSendWait;

        public IInstrumentState Instrument => this.instrument;

        public IConnection Connection => this.connection;

        public InstrumentManager()
        {
            this.connection = new SerialConnection();
            this.instrument = new GenericInstrumentState();
            connection.PacketReceived += Conn_PacketReceived;
            this.timer = new System.Timers.Timer(990)
            {
                AutoReset = true,
                Enabled = false,
            };
            this.timer.Elapsed += Timer_Elapsed;
            this.aliveKeeper = new ConnectionAliveKeeper(this, connection, logger);
            this._beatProtocolManager = new BeatProtocolManager(this, logger);
        }

        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (this.instrument.InstrumentConnected && !this.IsOnline)
            {
                ChangeInstrumentConnected(false);
            }
            aliveKeeper.Tick();
            _beatProtocolManager.Tick();
        }

        private void Conn_PacketReceived(object sender, PacketReceivedEventArgs e)
        {
            _latestReceivedMills = Environment.TickCount;
            if (dispatcher == null)
            {
                return;
            }

            if (sendPacketToWaitResponse != null && sendPacketToWaitResponse.Command == e.Packet.Command)
            {
                sendPacketToWaitResponse = null;
                responsePacketForSendWait = e.Packet;
                waitResponseSignalEvent.Set();
            }

            if (e.Packet.Command == CtrlCmds.STATE)
            {
                dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                {
                    try
                    {
                        var newInfo = PacketHelper.DecodeInstrumentState(e.Packet);
                        ISet<string> changedPropertyNames = instrument.AcceptChange(newInfo);
                        if (changedPropertyNames.Count > 0)
                        {
                            logger.Info("[" + string.Join(",", changedPropertyNames) + "],changed:" + newInfo);
                            NotifyStateChanged(new InstrumentInfoChangedEventArgs(instrument, changedPropertyNames));
                        }
                    }
                    catch (CommInvalidDataException ex)
                    {
                        logger.Error(ex.Message);
                    }
                }));
            }
            else if (e.Packet.Command == CtrlCmds.CONNECT)
            {
                if (e.Packet.Data != null && e.Packet.Data.ToLower() == "lancet01")
                {
                    dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                    {
                        ChangeInstrumentConnected(true);
                    }));
                }
            }
            this.PacketReceived?.Invoke(this, e);
        }

        private void ChangeInstrumentConnected(bool connected)
        {
            this.instrument.InstrumentConnected = connected;
            var changedPropertyNames = new HashSet<string>
                        {
                            PropertyConsts.InstrumentConnected
                        };
            NotifyStateChanged(new InstrumentInfoChangedEventArgs(instrument, changedPropertyNames));
        }

        public bool IsRunning => timer.Enabled;

        public void Start(Dispatcher d)
        {
            this.dispatcher = d;
            timer.Enabled = true;
        }

        public void Stop()
        {
            timer.Enabled = false;
        }

        public void Send(Packet packet)
        {
            if (this.IsOnline)
            {
                connection.Send(packet);
            }
        }

        public void SendChangeLedBrightness(byte ledBrightness)
        {
            if (!this.IsOnline)
                return;

            Send(PacketHelper.CreateLed(ledBrightness));
            instrument.LedBrightness = ledBrightness;
            var changedPropertyNames = new HashSet<string>
            {
                PropertyConsts.LedBrightness
            };
            NotifyStateChanged(new InstrumentInfoChangedEventArgs(instrument, changedPropertyNames));
        }

        public Packet SendAndWaitResponse(Packet packet)
        {
            try
            {
                waitResponseSignalEvent.Reset();
                if (this.IsOnline)
                {
                    connection.Send(packet);
                    sendPacketToWaitResponse = packet;
                    if (!waitResponseSignalEvent.WaitOne(2000))
                    {
                        throw new TimeoutException();
                    }
                    return responsePacketForSendWait;
                }
                else
                {
                    throw new Exception("Instrument Not Connected");
                }
            }
            finally
            {
                responsePacketForSendWait = null;
                sendPacketToWaitResponse = null;
            }
        }

        public bool IsOnline => IsAlive(KEEP_ALIVE_MILLSECONDS);

        private bool IsAlive(int offlineMillseconds)
        {
            bool connected = connection.IsConnected && instrument.InstrumentConnected;
            if (connected)
            {
                long ticks = Environment.TickCount - _latestReceivedMills;
                if (ticks < 0)
                {
                    _latestReceivedMills = Environment.TickCount;
                }
                if (ticks > offlineMillseconds)
                {
                    // 可能掉线了
                    connected = false;
                }
            }
            return connected;
        }

        public void ChangeVideoRecording(bool recording)
        {
            if (this.instrument.VideoRecording != recording)
            {
                this.instrument.VideoRecording = recording;
                var changedPropertyNames = new HashSet<string>
                {
                    PropertyConsts.VideoRecording
                };
                NotifyStateChanged(new InstrumentInfoChangedEventArgs(instrument, changedPropertyNames));
            }
        }
    }
}